/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						     high_scores.cpp

	$Header: /game/high_scores.cpp $

	$NoKeywords: $

 ************************************************************************/

#include "precompiled.h"
#pragma warning( disable: 4786 )

#include <fstream>
#include <strstream>
#include <stdio.h>

#include "high_scores.h"

#include "debug_message.h"

#include "adventure_map.h"
#include "compressed_filter.h"
#include "elements_of.h"
#include "external_string.h"
#include "format_string.h"
#include "handler.h"
#include "keyword_replacer.h"
#include "owned_ptr.h"
#include "platform.h"
#include "replace_keywords.h"
#include "score_computer.h"
#include "simple_dialog.h"
#include "sized_int_types.h"
#include "streambuf_operators.h"

extern t_external_string const k_text_open_failure;

namespace
{
	int const k_version = 0;
	std::string const k_scores_file_name = "data\\high_scores.dat";

	t_external_string const k_player_name_1( "player_name_1.high_score" );
	t_external_string const k_player_name_2( "player_name_2.high_score" );
	t_external_string const k_player_name_3( "player_name_3.high_score" );
	t_external_string const k_player_name_4( "player_name_4.high_score" );
	t_external_string const k_player_name_5( "player_name_5.high_score" );
	t_external_string const k_player_name_6( "player_name_6.high_score" );
	t_external_string const k_player_name_7( "player_name_7.high_score" );
	t_external_string const k_player_name_8( "player_name_8.high_score" );
	t_external_string const k_player_name_9( "player_name_9.high_score" );
	t_external_string const k_player_name_10( "player_name_10.high_score" );
	t_external_string const k_player_name_campaign_1( "player_name_campaign_1.high_score" );
	t_external_string const k_player_name_campaign_2( "player_name_campaign_2.high_score" );
	t_external_string const k_player_name_campaign_3( "player_name_campaign_3.high_score" );
	t_external_string const k_player_name_campaign_4( "player_name_campaign_4.high_score" );
	t_external_string const k_player_name_campaign_5( "player_name_campaign_5.high_score" );
	t_external_string const k_player_name_campaign_6( "player_name_campaign_6.high_score" );
	t_external_string const k_player_name_campaign_7( "player_name_campaign_7.high_score" );
	t_external_string const k_player_name_campaign_8( "player_name_campaign_8.high_score" );
	t_external_string const k_player_name_campaign_9( "player_name_campaign_9.high_score" );
	t_external_string const k_player_name_campaign_10( "player_name_campaign_10.high_score" );

	t_external_string const k_map_name_1( "map_name_1.high_score" );
	t_external_string const k_map_name_2( "map_name_2.high_score" );
	t_external_string const k_map_name_3( "map_name_3.high_score" );
	t_external_string const k_map_name_4( "map_name_4.high_score" );
	t_external_string const k_map_name_5( "map_name_5.high_score" );
	t_external_string const k_map_name_6( "map_name_6.high_score" );
	t_external_string const k_map_name_7( "map_name_7.high_score" );
	t_external_string const k_map_name_8( "map_name_8.high_score" );
	t_external_string const k_map_name_9( "map_name_9.high_score" );
	t_external_string const k_map_name_10( "map_name_10.high_score" );
	t_external_string const k_campaign_name_1( "campaign_name_1.high_score" );
	t_external_string const k_campaign_name_2( "campaign_name_2.high_score" );
	t_external_string const k_campaign_name_3( "campaign_name_3.high_score" );
	t_external_string const k_campaign_name_4( "campaign_name_4.high_score" );
	t_external_string const k_campaign_name_5( "campaign_name_5.high_score" );
	t_external_string const k_campaign_name_6( "campaign_name_6.high_score" );
	t_external_string const k_campaign_name_7( "campaign_name_7.high_score" );
	t_external_string const k_campaign_name_8( "campaign_name_8.high_score" );
	t_external_string const k_campaign_name_9( "campaign_name_9.high_score" );
	t_external_string const k_campaign_name_10( "campaign_name_10.high_score" );

	int k_initial_days[t_high_scores::k_num_score_entries] = 
	{
		15,	
		22,	
		21,	
		37,
		41,	
		29,	
		51,
		44,	
		38,	
		55,	
	};

	int k_initial_score[t_high_scores::k_num_score_entries] = 
	{
		294,
		278,
		249,
		228,
		215,
		186,
		177,
		156,
		130,
		118,
	};

	int k_initial_camp_days[t_high_scores::k_num_score_entries] = 
	{
		23,
		27,
		29,
		31,
		51,
		28,
		31,
		52,
		12,
		57,
	};

	int k_initial_camp_score[t_high_scores::k_num_score_entries] = 
	{
		1455,
		1380,
		1265,
		1185,
		1070,
		975,
		845,
		765,
		685,
		555,
	};


	// ------------------------------------------------------------
	// get the folder and filename where high scores are saved
	// ------------------------------------------------------------
	std::string const get_file_name()
	{
		std::string folder = get_current_folder();
		if( folder[folder.length()-1] != '\\' )
			folder += '\\';

		folder += k_scores_file_name;

		return folder;
	}

	// ------------------------------------------------------------
	// helper function to read in a single score table entry
	// ------------------------------------------------------------
	bool read_score_entry( std::streambuf & stream, t_high_score_entry * entry_ptr )
	{
		t_high_score_entry & entry = *entry_ptr;

		entry.set_portrait_type( get< t_creature_type >( stream ) );

		std::string text;

		if( read_string16( stream, text ) )
			entry.set_days( text );
		else
			return false;

		if( read_string16( stream, text ) )
			entry.set_score( text );
		else
			return false;

		if( read_string16( stream, text ) )
			entry.set_player_name( text );
		else
			return false;

		if( read_string16( stream, text ) )
			entry.set_map_name( text );
		else
			return false;

		return true;
	}

	// ------------------------------------------------------------
	// helper function to write a single score table entry
	// ------------------------------------------------------------
	bool write_score_entry( std::streambuf & stream, t_high_score_entry const * entry )
	{
		put< t_creature_type >( stream, entry->get_portrait_type() );

		if( !write_string16( stream, entry->get_days() ) )
			return false;
		if( !write_string16( stream, entry->get_score() ) )
			return false;
		if( !write_string16( stream, entry->get_player_name() ) )
			return false;
		if( !write_string16( stream, entry->get_map_name() ) )
			return false;

		return true;
	}
} // unnamed namespace

// ------------------------------------------------------------
// ------------------------------------------------------------
t_high_scores::t_high_scores() 
{
	build_default_scores_table();
}

// ------------------------------------------------------------
// ------------------------------------------------------------
void t_high_scores::build_default_scores_table()
{
	t_creature_type portrait[k_num_score_entries];
	int i;

	for( i = k_num_score_entries-1; i >= 0; --i )
	{
		portrait[i] = get_rank_creature( k_initial_score[i], k_creature_score_range );
	}

	// add standard default scores
	std::string text_days = format_string( "%d", k_initial_days[0] );
	std::string text_score = format_string( "%d", k_initial_score[0] );
	set_standard_entry( 0, text_days, k_map_name_1, k_player_name_1, portrait[0], text_score ); 
	text_days = format_string( "%d", k_initial_days[1] );
	text_score = format_string( "%d", k_initial_score[1] );
	set_standard_entry( 1, text_days, k_map_name_2, k_player_name_2, portrait[1], text_score ); 
	text_days = format_string( "%d", k_initial_days[2] );
	text_score = format_string( "%d", k_initial_score[2] );
	set_standard_entry( 2, text_days, k_map_name_3, k_player_name_3, portrait[2], text_score ); 
	text_days = format_string( "%d", k_initial_days[3] );
	text_score = format_string( "%d", k_initial_score[3] );
	set_standard_entry( 3, text_days, k_map_name_4, k_player_name_4, portrait[3], text_score ); 
	text_days = format_string( "%d", k_initial_days[4] );
	text_score = format_string( "%d", k_initial_score[4] );
	set_standard_entry( 4, text_days, k_map_name_5, k_player_name_5, portrait[4], text_score ); 
	text_days = format_string( "%d", k_initial_days[5] );
	text_score = format_string( "%d", k_initial_score[5] );
	set_standard_entry( 5, text_days, k_map_name_6, k_player_name_6, portrait[5], text_score ); 
	text_days = format_string( "%d", k_initial_days[6] );
	text_score = format_string( "%d", k_initial_score[6] );
	set_standard_entry( 6, text_days, k_map_name_7, k_player_name_7, portrait[6], text_score ); 
	text_days = format_string( "%d", k_initial_days[7] );
	text_score = format_string( "%d", k_initial_score[7] );
	set_standard_entry( 7, text_days, k_map_name_8, k_player_name_8, portrait[7], text_score ); 
	text_days = format_string( "%d", k_initial_days[8] );
	text_score = format_string( "%d", k_initial_score[8] );
	set_standard_entry( 8, text_days, k_map_name_9, k_player_name_9, portrait[8], text_score ); 
	text_days = format_string( "%d", k_initial_days[9] );
	text_score = format_string( "%d", k_initial_score[9] );
	set_standard_entry( 9, text_days, k_map_name_10, k_player_name_10, portrait[9], text_score ); 

	// add campaign default scores
	for( i = 9; i >= 0; --i )
	{
		portrait[i] = get_rank_creature( k_initial_camp_score[i], k_creature_score_range*k_campaign_score_multiplier );
	}
	
	// add standard default scores
	text_days = format_string( "%d", k_initial_camp_days[0] );
	text_score = format_string( "%d", k_initial_camp_score[0] );
	set_campaign_entry( 0, text_days, k_campaign_name_1, k_player_name_campaign_1, portrait[0], text_score ); 
	text_days = format_string( "%d", k_initial_camp_days[1] );
	text_score = format_string( "%d", k_initial_camp_score[1] );
	set_campaign_entry( 1, text_days, k_campaign_name_2, k_player_name_campaign_2, portrait[1], text_score ); 
	text_days = format_string( "%d", k_initial_camp_days[2] );
	text_score = format_string( "%d", k_initial_camp_score[2] );
	set_campaign_entry( 2, text_days, k_campaign_name_3, k_player_name_campaign_3, portrait[2], text_score ); 
	text_days = format_string( "%d", k_initial_camp_days[3] );
	text_score = format_string( "%d", k_initial_camp_score[3] );
	set_campaign_entry( 3, text_days, k_campaign_name_4, k_player_name_campaign_4, portrait[3], text_score ); 
	text_days = format_string( "%d", k_initial_camp_days[4] );
	text_score = format_string( "%d", k_initial_camp_score[4] );
	set_campaign_entry( 4, text_days, k_campaign_name_5, k_player_name_campaign_5, portrait[4], text_score ); 
	text_days = format_string( "%d", k_initial_camp_days[5] );
	text_score = format_string( "%d", k_initial_camp_score[5] );
	set_campaign_entry( 5, text_days, k_campaign_name_6, k_player_name_campaign_6, portrait[5], text_score ); 
	text_days = format_string( "%d", k_initial_camp_days[6] );
	text_score = format_string( "%d", k_initial_camp_score[6] );
	set_campaign_entry( 6, text_days, k_campaign_name_7, k_player_name_campaign_7, portrait[6], text_score ); 
	text_days = format_string( "%d", k_initial_camp_days[7] );
	text_score = format_string( "%d", k_initial_camp_score[7] );
	set_campaign_entry( 7, text_days, k_campaign_name_8, k_player_name_campaign_8, portrait[7], text_score ); 
	text_days = format_string( "%d", k_initial_camp_days[8] );
	text_score = format_string( "%d", k_initial_camp_score[8] );
	set_campaign_entry( 8, text_days, k_campaign_name_9, k_player_name_campaign_9, portrait[8], text_score ); 
	text_days = format_string( "%d", k_initial_camp_days[9] );
	text_score = format_string( "%d", k_initial_camp_score[9] );
	set_campaign_entry( 9, text_days, k_campaign_name_10, k_player_name_campaign_10, portrait[9], text_score ); 
}

// ------------------------------------------------------------
// ------------------------------------------------------------
bool t_high_scores::read( std::streambuf & stream )
{	  
	t_uint16 version = get< t_uint16 >( stream );

	if( version != k_version )
		return false;

	int i;

	// read in the standard scores block
	for( i = 0; i < ELEMENTS_OF( m_standard_scores ); ++i )
	{
		if( !read_score_entry( stream, &m_standard_scores[i] ) )
			return false;
	}

	// read in the campaign scores block
	for( i = 0; i < ELEMENTS_OF( m_campaign_scores ); ++i )
	{
		if( !read_score_entry( stream, &m_campaign_scores[i] ) )
			return false;
	}

	return true;
}

// ------------------------------------------------------------
// ------------------------------------------------------------
t_high_score_entry::t_high_score_entry( std::string const & days, 
						std::string const & map_name,
						std::string const & player_name,
						t_creature_type		portrait_type,
						std::string const & score )	:
						m_days( days ),
						m_map_name( map_name ),
						m_player_name( player_name ),
						m_portrait_type( portrait_type ),
						m_score( score ),
						m_highlighted( false )
{
	if ( m_portrait_type < 0 || m_portrait_type >= k_creature_type_count )
	{
		assert( false );
		m_portrait_type = static_cast<t_creature_type>(0);
	}
}

// ------------------------------------------------------------
// ------------------------------------------------------------
std::string const & t_high_score_entry::get_days() const
{
	return m_days;
}

// ------------------------------------------------------------
// ------------------------------------------------------------
std::string const & t_high_score_entry::get_map_name() const
{
	return m_map_name;
}

// ------------------------------------------------------------
// ------------------------------------------------------------
std::string const & t_high_score_entry::get_player_name() const
{
	return m_player_name;
}

// ------------------------------------------------------------
// ------------------------------------------------------------
t_creature_type t_high_score_entry::get_portrait_type() const
{
	assert( m_portrait_type >= k_creature_none && m_portrait_type < k_creature_type_count );
	return m_portrait_type;
}

// ------------------------------------------------------------
// ------------------------------------------------------------
std::string const & t_high_score_entry::get_score() const
{
	return m_score;
}

// ------------------------------------------------------------
// ------------------------------------------------------------
void t_high_score_entry::set( std::string const & days, 
							  std::string const & map_name,
							  std::string const & player_name,
							  t_creature_type	 portrait_type,
							  std::string const & score )
{
	m_days			= days;
	m_map_name		= map_name;
	m_player_name	= player_name;
	set_portrait_type( portrait_type );
	m_score			= score;
}

// ------------------------------------------------------------
// ------------------------------------------------------------
void t_high_score_entry::set_days( std::string const & days )
{
	m_days = days;
}

// ------------------------------------------------------------
// ------------------------------------------------------------
void t_high_score_entry::set_map_name( std::string const & map_name )
{
	m_map_name = map_name;
}

// ------------------------------------------------------------
// ------------------------------------------------------------
void t_high_score_entry::set_player_name( std::string const & player_name )
{
	m_player_name = player_name;
}

// ------------------------------------------------------------
// ------------------------------------------------------------
void t_high_score_entry::set_portrait_type( t_creature_type type )
{
	if ( type < k_creature_none || type >= k_creature_type_count )
	{
		assert( false );
		type = k_creature_none;
	}

	m_portrait_type = type;
}

// ------------------------------------------------------------
// ------------------------------------------------------------
void t_high_score_entry::set_score( std::string const & score )
{
	m_score = score;
}

// ------------------------------------------------------------
// ------------------------------------------------------------
t_high_score_entry * t_high_scores::get_campaign_entry( int ndx )
{
	if( ndx >= 0 && ndx < ELEMENTS_OF( m_campaign_scores ) )
	{
		return &m_campaign_scores[ndx];
	} else
		return &m_campaign_scores[0];
}

// ------------------------------------------------------------
// ------------------------------------------------------------
t_high_score_entry * t_high_scores::get_standard_entry( int ndx )
{
	if( ndx >= 0 && ndx < ELEMENTS_OF( m_standard_scores ) )
	{
		return &m_standard_scores[ndx];
	} else
		return &m_standard_scores[0];
}

// ------------------------------------------------------------
// ------------------------------------------------------------
void t_high_scores::set_campaign_entry( int ndx,
										std::string const & days, 
										std::string const & map_name,
										std::string const & player_name,
										t_creature_type		portrait_type,
										std::string const & score )
{
	assert( ndx < ELEMENTS_OF( m_campaign_scores ) );
	m_campaign_scores[ndx].set( days, map_name, player_name, portrait_type, score );
}

// ------------------------------------------------------------
// ------------------------------------------------------------
void t_high_scores::set_standard_entry( int ndx,
										std::string const & days, 
										std::string const & map_name,
										std::string const & player_name,
										t_creature_type		portrait_type,
										std::string const & score )
{
	assert( ndx < ELEMENTS_OF( m_standard_scores ) );
	m_standard_scores[ndx].set( days, map_name, player_name, portrait_type, score );
}

// ------------------------------------------------------------
// ------------------------------------------------------------
bool t_high_scores::write( std::streambuf& stream ) const
{
	put< t_uint16 >( stream, k_version );
	 
	int i;

	// write the standard scores block
	for( i = 0; i < ELEMENTS_OF( m_standard_scores ); ++i )
	{
		if( !write_score_entry( stream, &m_standard_scores[i] ) )
			return false;
	}

	// write the campaign scores block
	for( i = 0; i < ELEMENTS_OF( m_campaign_scores ); ++i )
	{
		if( !write_score_entry( stream, &m_campaign_scores[i] ) )
			return false;
	}

	return true;
}


// ------------------------------------------------------------
// see if we qualify for a new high score entry
// ------------------------------------------------------------
bool t_high_scores::check_high_scores(	std::string const &		title,
										int						days,
										int						final_score, 
										std::string	 const &	player_name,
										bool					is_standard,
										t_creature_type			portrait_type )
{
	// Decide which array to check
	t_high_score_entry * array = (is_standard) ? m_standard_scores : m_campaign_scores;

	assert( ELEMENTS_OF( m_standard_scores ) == ELEMENTS_OF( m_campaign_scores ) );

	for( int i = 0; i < ELEMENTS_OF( m_standard_scores ); ++i )
	{
		if( final_score > atoi( array[i].get_score().c_str() ) )
		{
			// copy the other remaining scores down a notch and drop the last
			// score off the bottom
			for( int j = k_num_score_entries - 1; j > i; --j )
			{
				array[j] = array[j-1];
			}

			// now add in the new score
			array[i].set_highlighted( true );				
			array[i].set( format_string( "%d", days ),
						  title,
						  player_name,
						  portrait_type,
						  format_string( "%d", final_score ) );
			return true;
		}
	}

	return false; // Sorry, no high score for you!
}

// ------------------------------------------------------------
// read a high scores file
// ------------------------------------------------------------	  
static t_external_string const k_text_high_score_read_problem( "high_score_read_problem.misc" );
t_high_scores_ptr read_high_scores_file()
{
	std::filebuf	file;
	std::string		text;
	bool			result = false;
	t_high_scores_ptr high_scores = new t_high_scores();

	// Try to open the input file
	result = (file.open( get_file_name().c_str(), std::ios::in | std::ios::binary ) != NULL);

	if ( result )
	{
		// Extract the data from the file
		t_inflate_filter stream( file );
		result = high_scores->read( stream );
	}
	return high_scores;
}

// ------------------------------------------------------------
// write a high scores file
// ------------------------------------------------------------
bool write_high_scores_file( t_high_scores_ptr const high_scores )
{
	std::filebuf	file;
	std::string		text;
	bool			result;

	if( file.open( get_file_name().c_str(), std::ios::out | std::ios::binary ) == 0 )
	{
		text = replace_keywords( k_text_open_failure, "%file_name", get_file_name() );
		ok_dialog( text, true );
		result = false;
	}
	else
	{
		// This MUST be in a code block to force destructor of the decompression stream BEFORE 
		// the file goes away because the decompression stream doesn't flush (even 
		// if you tell it to) until it is actually destroyed.
		t_deflate_filter	stream( file );

		result = high_scores->write( stream );
	}
	return result;
}

